package cicd

import (
	"fmt"
	"time"
	"hulk-api/util/common"
	"strings"
	"io/ioutil"
	db "hulk-api/databases"
	"encoding/json"
	"log"
	"github.com/garyburd/redigo/redis"
	"hulk-api/models"
	"errors"
	"mime/multipart"
	"os"
	"io"
)

type DeploymentFlow struct {
	Id int 					`json:"id"`
	Title string 			`json:"title"`
	Content string 			`json:"content"`
	OnlineTime string 		`json:"onlinetime"`
	ItemList string 		`json:"itemlist"`
	DbList string 			`json:"dblist"`
	SqlPathList string 		`json:"sqlpathlist"`
	CreateTime string 		`json:"createtime"`
	SqlList string 			`json:"sqllist"`
	Status string			`json:"status"`

	TestReportFilename string		`json:"reportFile"`
	TestReportFilepath string		`json:"reportFilepath"`
	TestReportContent multipart.File	`json:"reportContent"`

}

func (job *DeploymentFlow) Create() (  err error) {
	//入库
	fmt.Println("begin to create job...................")

	fmt.Printf("%+v\n", job)


	job.CreateTime = time.Now().Format("2006-01-02 15:04:05")

	sqldir := "static/deploymentJobs/" + time.Now().Format("2006-01-02") + "/" + time.Now().Format("150405")  //还有有冲突的可能性
	//fmt.Println("debug to mkdir path" , sqldir)

	err = common.CreateIfNotExist(sqldir)
	if err != nil {
		fmt.Println("创建任务sql目录失败" , err.Error())
	}

	sqlpathlist := ""
	if strings.TrimSpace(job.DbList) != "" {
		dblist := strings.Split(job.DbList," ")
		sqlcontent := strings.Split(job.SqlList,"|")


		for i,d := range dblist {
			p := sqldir + "/" + strings.TrimSpace(d) + ".sql"
			sqlpathlist = sqlpathlist + p + ","
			err = ioutil.WriteFile(p,[]byte(sqlcontent[i] ),0666)
			if err != nil {
				fmt.Println("保存sql：",err.Error())
			}

		}
	}

	//保存测试报告
	reportPath := sqldir + "/" + job.TestReportFilename
	out, err := os.Create(reportPath)
	if err != nil {
		//log.Fatal(err)
		fmt.Println(err)
	}
	defer out.Close()
	_, err = io.Copy(out, job.TestReportContent)
	if err != nil {
		fmt.Println(err)
	}




	stmt, err := db.SqlDB.Prepare("insert into deployment_job(title,content,online_time,db_list,create_datetime,sql_path_list,report_path,item_list) values(?,?,?,?,?,?,?,?)")
	if err != nil {
		fmt.Println("here error is ..." , err.Error())
		return
	}
	defer stmt.Close()
	res, err := stmt.Exec(job.Title,job.Content,job.OnlineTime,strings.TrimSpace(job.DbList),job.CreateTime,sqlpathlist,reportPath,job.ItemList)
	if err != nil {
		fmt.Println("insert into error " , err.Error())
		return
	}



	lastid,_ := res.LastInsertId()
	job.Id = int(lastid)
	go job.InitDeploymentStatusById()   //存入redis redis暂时没有json数据结构 蛋疼
	return

}

type Status struct {
	Sqlloading bool						`json:"sqlloading"`
	Dbjob map[string]IconStyle			`json:"dbjob"`
	Itemloading bool					`json:"itemloading"`
	Itemjob map[string]IconStyle		`json:"itemjob"`
}

type IconStyle struct {
	Icon string							`json:"icon"`
	IconType string						`json:"type"`
	Result string						`json:"result"`   //执行结果，存放在redis中
	Button bool							`json:"button"`   //能否手动执行按钮，存放在redis中
}

func (job *DeploymentFlow)InitDeploymentStatusById()  {
	redisconn := db.RedisPool.Get()
	defer redisconn.Close()
	d := make(map[string]IconStyle)
	i := make(map[string]IconStyle)
	for _,dbname := range strings.Split(job.DbList," ") {
		d[dbname] = IconStyle{
			Icon: "el-icon-more-outline",
			IconType: "info",
			Result: "尚未执行",
			Button: false,
		}
	}
	for _,item := range strings.Split(job.ItemList," ") {
		i[item] = IconStyle{
			Icon: "el-icon-more-outline",
			IconType: "info",
			Result: "尚未发布",
			Button: false,
		}
	}
	s := Status{
		Sqlloading: false,
		Dbjob: d,
		Itemloading:false,
		Itemjob: i,
	}
	if b,err := json.Marshal(s); err == nil {
		log.Println("开始初始化任务，id: " , job.Id)
		_,err =redisconn.Do("SET",job.Id,string(b))
		if err != nil {
			log.Println("set redis key 失败 ,id: ",job.Id , "  " , err.Error())
		}
	} else {
		log.Println("任务初始化失败，id:", job.Id ,err.Error())
	}
}


func (job *DeploymentFlow) GetDeployJobStatus() ( jsonstring string , err error) {

	redisconn := db.RedisPool.Get()
	defer redisconn.Close()
	/*
	TODO:redis连接失败
	TODO: key被误删除
	*/
	jsonstring,err = redis.String(redisconn.Do("GET", job.Id))
	return

}

func (job *DeploymentFlow) GetDeployJobs() ( jobs []DeploymentFlow , err error) {

	jobs = make([]DeploymentFlow,0)
	//sql := fmt.Sprintf(" select account,name,email,department,role,lastLoginTime from user")
	rows, err := db.SqlDB.Query("select id,title,content,item_list,db_list,sql_path_list,report_path,create_datetime,status from deployment_job order by id desc")

	defer rows.Close()
	for rows.Next() {
		var j DeploymentFlow
		rows.Scan(&j.Id,&j.Title,&j.Content,&j.ItemList,&j.DbList,&j.SqlPathList,&j.TestReportFilepath,&j.CreateTime,&j.Status)
		jobs = append(jobs, j)
	}
	if err = rows.Err(); err != nil {
		return
	}

	return

}


func (job *DeploymentFlow) Start() (  err error) {
	//step1 获取任务列表
	job.lock()
	defer job.unlock()


	row := db.SqlDB.QueryRow("select item_list,db_list,sql_path_list from deployment_job where id= ?" , job.Id)
	//row.scan中的字段必须是按照数据库存入字段的顺序，否则报错
	if err =row.Scan(&job.ItemList,&job.DbList,&job.SqlPathList); err != nil{
		fmt.Printf("scan failed, err:%v",err)
		return
	}


	//step2 获取状态初始化数据-本来可以直接操作redis对应某个步骤状态
	//现在只能一个步骤一次set

	scriptJob := strings.Split(job.DbList," ")
	scriptPath := strings.Split(job.SqlPathList,",")
	itemJob := strings.Split(job.ItemList," ")

	d := make(map[string]IconStyle)
	i := make(map[string]IconStyle)
	for _,dbname := range scriptJob  {
		d[dbname] = IconStyle{
			Icon: "el-icon-more-outline",
			IconType: "info",
			Result: "尚未执行",
			Button: false,
		}
	}
	for _,item := range itemJob {
		i[item] = IconStyle{
			Icon: "el-icon-more-outline",
			IconType: "info",
			Result: "尚未发布",
			Button: false,
		}
	}
	style := Status{
		Sqlloading: false,
		Dbjob: d,
		Itemloading:false,
		Itemjob: i,
	}

	//step3 开始执行scriptJob
	//设置为加载中
	style.Sqlloading = true   //提交 会频繁的进行json序列化
	iconSet(job.Id,style) //不能异步，有可能会出现乱序

	for i,d := range scriptJob {
		//db.do()  //执行sql
		//但是这个是执行文件 一个文件包含多个sql语句
		j := models.SqlJob{
			Dbname: d,
			SqlPath: scriptPath[i],
		}
		time.Sleep(5 * time.Second)  //模拟5秒完成,并成功
		_,err = j.DoBySqlpath()  //暂时忽略成功的执行结果 _
		if err != nil {
			style.Sqlloading = false
			style.Dbjob[d] = IconStyle{
				Icon: "el-icon-close",
				IconType: "danger",
				Result: err.Error(),
				Button: false,
			}
			iconSet(job.Id,style) //异步
			go job.breakOff()
			return //遇错，即终止任务的继续执行
		}

		style.Dbjob[d] = IconStyle{
			Icon: "el-icon-check",
			IconType: "success",
			Result: "执行成功",
			Button: true,
		}
		iconSet(job.Id,style) //异步
	}
	style.Sqlloading = false
	iconSet(job.Id,style)

	//step3 开始执行itemJob
	style.Itemloading = true
	iconSet(job.Id,style)

	for _,i := range itemJob {
		//db.do()
		var item Deployment
		item.Name = i

		errr := item.loadDeployItemByname()
		if errr != nil {
			fmt.Println("load item by name is fail " , item.Name)
		}


		for {
			is,_ := item.IsRuning() //忽略redis返回的错误，加锁失败
			if is {
				fmt.Println("该项目正在构建中，请稍后....")
				time.Sleep(1 * time.Second)
			}else {
				fmt.Println("即将构建项目 ：" , item.Name)
				break
			}
		}
		var id string  //任务中item构建的ID
		id,err = item.Start()

		if err != nil {

			fmt.Println("item job start statussssssssssssssss is " , err.Error())

			style.Itemloading = false
			style.Itemjob[i] = IconStyle{
				Icon: "el-icon-close",
				IconType: "danger",
				//Result: err.Error(), 不直接返回错误 直接返回工作空间的ID
				Result: "Output: #" + id,
				Button: false,
			}
			iconSet(job.Id,style) //异步
			go job.breakOff()
			return //遇错，即终止任务的继续执行
		}


		//time.Sleep(20 * time.Second)  //模拟5秒完成,并成功
		style.Itemjob[i] = IconStyle{
			Icon: "el-icon-check",
			IconType: "success",
			Result: "发布成功",
			Button: true,
		}
		iconSet(job.Id,style)
	}
	style.Itemloading = false
	iconSet(job.Id,style)

	go job.Close()

	return

}


func  (job *DeploymentFlow) lock()  {
	key := fmt.Sprintf("deployment-%d" , job.Id)
	redisconn := db.RedisPool.Get()
	defer redisconn.Close()
	_, err := redisconn.Do("SET", key, "starting")
	//defer
	if err != nil {
		log.Println("redis set error:", err)
	}

}

func  (job *DeploymentFlow) unlock()  {
	key := fmt.Sprintf("deployment-%d" , job.Id)
	redisconn := db.RedisPool.Get()
	defer redisconn.Close()
	_, err := redisconn.Do("DEL", key)
	if err != nil {
		fmt.Println("redis del error:", err)   //暂时不做处理
	}

}

func iconSet(id int , val Status)  {
	fmt.Printf("现在状态是   %+v\n", val)
	redisconn := db.RedisPool.Get()
	defer redisconn.Close()

	if b,err := json.Marshal(val); err == nil {
		fmt.Println("开始改变任务状态，id: " , id)
		_,err =redisconn.Do("SET",id,string(b))
		if err != nil {
			fmt.Println("set redis key 失败 ,id: ",id , "  " , err.Error())
		}
	} else {
		fmt.Println("开始改变任务状态，id:", id ,err.Error())
	}

}

func (job *DeploymentFlow) breakOff()   {
	_, e := db.SqlDB.Exec("update deployment_job set status=1 where id=?"  , job.Id)
	if e != nil {
		log.Println("设置中止状态失败。。。 " , e.Error())
	}
}

func (job  *DeploymentFlow) Close()   {
	_, e := db.SqlDB.Exec("update deployment_job set status=2 where id=?"  , job.Id)
	if e != nil {
		fmt.Println("设置中止状态失败。。。 ")
	}
}

func (job *DeploymentFlow) getLock() ( b bool ,err error)  {
	key := fmt.Sprintf("deployment-%d" , job.Id)
	redisconn := db.RedisPool.Get()
	defer redisconn.Close()
	exist, err := redis.Int(redisconn.Do("EXISTS",key))
	if exist==1 {
		fmt.Println("deployment is string...")
		b = false
	} else {
		b = true
	}
	return
}

func (job *DeploymentFlow) GetLock() ( b bool ,err error)  {
	b,err = job.getLock()
	return
}

func (job *DeploymentFlow) Delete() (err error)   {
	//TODO: 加锁，如果job上线任务在执行中，不能删除
	lock,_ := job.getLock()
	if !lock {
		err = errors.New(fmt.Sprintf("该任务正在执行中，禁止删除；任务ID：%d " , job.Id))
		return
	}

	_, e := db.SqlDB.Exec("delete from deployment_job  where id=?"  , job.Id)
	if e != nil {
		fmt.Println("设置中止状态失败。。。 ")
	}
	return
}